﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Threading.Tasks;
using System.Threading;

namespace Sl.Duplex {
	public class SerialPortDp : IDuplex {
		public override string Name => sp.PortName;
		public override bool IsRunning => sp.IsOpen;
		public System.IO.Ports.SerialPort sp;
		int max_write_delay => (sp.WriteBufferSize * 1000) / sp.BaudRate + 50;
		public SerialPortDp() {
			sp = new System.IO.Ports.SerialPort();
			sp.ReadBufferSize = 4096;
			sp.WriteBufferSize = 4096;
			sp.DataReceived += Sp_DataReceived;
			sp.ErrorReceived += Sp_ErrorReceived;
			sp.ReadTimeout = 0;
			sp.WriteTimeout = 200;
		}
		
		public SerialPortDp(string sp_cfg, out string warning) {
			sp = new System.IO.Ports.SerialPort();
			warning = config(sp_cfg);
			sp.ReadBufferSize = 4096;
			sp.WriteBufferSize = 4096;
			sp.DataReceived += Sp_DataReceived;
			sp.ErrorReceived += Sp_ErrorReceived;
			sp.ReadTimeout = 0;
			sp.WriteTimeout = 200;
		}

		public override string config(string str) {
			if (IsRunning) {
				return "【错误】正在运行，不接受配置。";
			}
			return sp.init(str); 
		}


		bool is_disposed = false;
		protected override void Dispose(bool disposing) {
			base.Dispose(disposing);
			if (disposing) {
				if (false == is_disposed) {
					is_disposed = true;
					closePort();
					sp.DataReceived -= Sp_DataReceived;
					sp.ErrorReceived -= Sp_ErrorReceived;
					sp.Dispose();
				}
			}
		}
		public override void openPort() {
			if (IsRunning) {
				return;
			}
			using (var cts = new CancellationTokenSource()) {
				cts.CancelAfter(500); // Set timeout
				var task = Task.Run(() =>
				{
					try {
						sp.Open();
					} catch (System.IO.IOException) {
						return;
					} catch (System.UnauthorizedAccessException) {
						return;
					}
				}, cts.Token);
				try {
					task.Wait(cts.Token);
				} catch (OperationCanceledException) {
					return;
				}
				onPortStatusChange();
			}
		}
		public override  void closePort() {
			if (IsRunning) {
				sp.Close();
				onPortStatusChange();
			}
		}
		private void Sp_ErrorReceived(object sender, SerialErrorReceivedEventArgs e) {
			//closePort();
		}
		private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e) {
			if (RecvOneByte != null) {
				while (true) {
					var temp = sp.ReadByte();
					if (temp < 0) {
						return ;
					}
					RecvOneByte(this, (byte)temp);
				}
			} else if (RecvMutBytes != null) {
				byte[] data = new byte[sp.BytesToRead];
				sp.Read(data, 0, data.Length);
				RecvMutBytes(this, data, 0, data.Length);
			} else {
				dDataToRead?.Invoke(this);
			}
		}
		public override int Read(byte[] buffer, int offset, int count) {
			if (sp.IsOpen==false) {
				return 0;
			}
			int rev = sp.Read(buffer, offset, count);
			countRead(rev);
			return rev;
		}		
		public override int ReadByte() {
			int rev = sp.ReadByte();
			if (rev >= 0) {
				countRead(1);
			}
			return rev;
		}
		public override void Write(byte[] buffer, int offset, int count) {
			countWrite(count);
			if (IsRunning == false) {
				openPort();
			}
			try {
				sp.BaseStream.Write(buffer, offset, count);
			} catch (IOException) {
				closePort();
				openPort();
				if (IsRunning) {
					sp.BaseStream.Write(buffer, offset, count);
				}
			}
		}
		public override void WriteByte(byte value) {
			countWrite(1);
			if (IsRunning == false) {
				openPort();
			}
			try {
				sp.BaseStream.WriteByte(value);
			} catch (IOException) {
				closePort();
				openPort();
				if (IsRunning) {
					sp.BaseStream.WriteByte(value);
				}
			}
		}
	}
}
